package org.ala.tiktools.sharddb.mybatis.executor;

import java.lang.reflect.Method;
import java.sql.SQLSyntaxErrorException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

import org.ala.tiktools.logs.LoggerFactory;
import org.ala.tiktools.mybatis.commons.SqlSessionTemplateHolder;
import org.ala.tiktools.mybatis.expand.ConfigurationExpander;
import org.ala.tiktools.mybatis.expand.ExpandData;
import org.ala.tiktools.mybatis.expand.binding.MapperMethodExpander;
import org.ala.tiktools.mybatis.spring.TikToolsMyBatisSpringContext;
import org.ala.tiktools.sharddb.exception.ShardDBRuntimeException;
import org.ala.tiktools.sharddb.mybatis.double_write.DoubleWriterHandler;
import org.ala.tiktools.sharddb.mybatis.interceptor.Shard;
import org.ala.tiktools.sharddb.mybatis.router.ShardInfo;
import org.ala.tiktools.sharddb.mybatis.transaction.ShardTransaction;
import org.ala.tiktools.tools.CachingKVTools;
import org.ala.tiktools.tools.kv.KV;
import org.ala.tiktools.tools.kv.Maps;
import org.ala.tiktools.tools.thread_pool.ThreadPoolTools;
import org.slf4j.Logger;

/**
 * 拿到确定的分库分表信息后的执行器
 *
 * @author ala
 * @date 2025-02-19 22:45
 */
public class ShardExecutor {


    static Logger log = LoggerFactory.getLogger("shard", "SqlSession");


    /**
     *  公共业务线程池
     */
    protected ThreadPoolExecutor commonsThreadPool = ThreadPoolTools.build(50, 100, 1024, "ShardDBThread");
    /**
     *  事务执行器
     */
    protected ShardTxExecutor txExecutor = new ShardTxExecutor();
    /**
     *  非事务执行器
     */
    protected ShardNoTxExecutor noTxExecutor = new ShardNoTxExecutor();
    /**
     *  双写执行器
     */
    protected DoubleWriterHandler doubleWriterHandler = new DoubleWriterHandler();


    /**
     *  执行分库分表方法
     */
    public Map<ShardInfo, Result> doShardInvoke(Shard anno, Map<ShardInfo, Object[]> shardInfoMap, Object proxy, Method method, Object[] args, Map<String, ExpandData> expandDataMap) throws Exception {
        Map<ShardInfo, ShardExecutor. Result> res = new HashMap<>();

        //  一个分库分表扔进一个线程池执行
        ThreadPoolExecutor threadPool = threadPoolMapper.get(method);

        //  检测method是否包含事务注解
        ShardTransaction txAnno = shardTransactonMapper.get(method);
        if (txAnno != null) {
            res = txExecutor.doShardInvokeWithTx(anno, txAnno, threadPool, shardInfoMap, proxy, method);
        }
        //  不含事务直接跑
        else {
            res = noTxExecutor.doShardInvokeWithoutTx(anno, threadPool, shardInfoMap, proxy, method, args);
        }

        //  检测是否存在执行失败的，只要有1个失败的就判定为执行失败
        for (Map.Entry<ShardInfo, ShardExecutor.Result> entity : res.entrySet()) {
            if (!entity.getValue().isSuccess()) {
                throw new ShardDBRuntimeException(entity.getValue().getE());
            }
        }

        //  针对事务操作，如果配置了双写，则需要额外执行双写
        if (txAnno != null) {
            doubleWriterHandler.doDoubleWrite(threadPool, proxy, method, args, shardInfoMap, res);
        }

        return res;
    }


    /**
     *  执行单体方法
     *  <p> 直接拿MyBatis的MapperMethod执行，否则要递归调method
     */
    public static Result doInvoke(Shard anno, ShardInfo shardInfo, Method method, Object[] args) {
        long start = System.currentTimeMillis();
        try {
//            ShardInfoThreadLocal.set(shardInfo);

            //  直接调用MapperMethod执行，省的递归反射
            return _doInvoke(anno, shardInfo, method, args, start);
        } catch (Exception e) {
            //  如果表不存在，并且配置了自动创建表，则自动建表后再试一次
            //  只判断3层
            if (anno.autoCreateTable() && isTableNotExist(e)) {
                try {
                    createTable(method, shardInfo.getShardTable());

                    return _doInvoke(anno, shardInfo, method, args, start);
                } catch (Exception ex) {
                    //  如果表已存在，继续执行（别的线程或别的服务已经建好了表）
                    if (isTableExist(ex)) {
                        return _doInvoke(anno, shardInfo, method, args, start);
                    }

                    return Result.build(shardInfo, false, args, null, ex, System.currentTimeMillis() - start);
                }
            }
            return Result.build(shardInfo, false, args, null, e, System.currentTimeMillis() - start);
        } finally {
//            ShardInfoThreadLocal.clear();
        }
    }
    @SuppressWarnings("unchecked")
	protected static Result _doInvoke(Shard anno, ShardInfo shardInfo, Method method, Object[] args, long start) {
        MapperMethodExpander mapperMethod = TikToolsMyBatisSpringContext.getBeanWithAssert(ConfigurationExpander.class).getMapperMethodHolder().cachedMapperMethod(method.getDeclaringClass(), method);
        Object r = mapperMethod.execute(SqlSessionTemplateHolder.getInstance(), args, Maps.build(KV.build("shardSuffix", ExpandData.build("shardSuffix", shardInfo.getShardTable(), true))));
        return Result.build(shardInfo, true, args, r, null, System.currentTimeMillis() - start);
    }
    /**
     *  判断是否是表没建引发的异常
     */
    protected static boolean isTableNotExist(Throwable e) {
        if (!(e instanceof SQLSyntaxErrorException)) {
            if (e.getCause() == null) {return false;}
            return isTableNotExist(e.getCause());
        }
        String msg = e.getMessage();
        return msg != null
                && msg.startsWith("Table") && msg.endsWith("doesn't exist");
    }
    /**
     *  判断是否是建表重复引发的异常
     */
    protected static boolean isTableExist(Throwable e) {
        if (!(e instanceof SQLSyntaxErrorException)) {
            if (e.getCause() == null) {return false;}
            return isTableExist(e.getCause());
        }
        String msg = e.getMessage();
        return msg != null
                && msg.startsWith("Table") && msg.endsWith("already exists");
    }
    /**
     *  建表
     */
    @SuppressWarnings("unchecked")
	protected static void createTable(Method method, String suffix) {
        String stmt = String.format("%s.%s.%s", method.getDeclaringClass().getPackage().getName(), method.getDeclaringClass().getSimpleName(), "createTable");
        SqlSessionTemplateHolder.getInstance().update(stmt, Maps.build(KV.build("shardSuffix", suffix)));
    }


    /**
     *  Method与线程池对应
     */
    protected CachingKVTools<Method, ThreadPoolExecutor> threadPoolMapper = new CachingKVTools<>(ThreadPoolTools.NULL, m -> {
        Shard anno = m.getAnnotation(Shard.class);
        if (anno.coreSize() <= 0 || anno.maxSize() <= 0 || anno.queueSize() <= 0) {
            return commonsThreadPool;
        }
        return ThreadPoolTools.build(anno.coreSize(), anno.maxSize(), anno.queueSize(), String.format("ShardDBThread_%s_%s", m.getDeclaringClass().getSimpleName(), m.getName()));
    });


    /**
     *  method与ShardTransacton注解的映射
     */
    protected CachingKVTools<Method, ShardTransaction> shardTransactonMapper = new CachingKVTools<>(ShardTxExecutor.NULL_SHARD_TRANSACTION, m -> {
        return m.getAnnotation(ShardTransaction.class);
    });


    /**
     *  执行结果
     */
    public static class Result {
        /**
         *  分库分表信息
         */
        protected ShardInfo shardInfo;
        /**
         *  执行是否成功
         */
        protected boolean success;
        /**
         *  执行方法时给的参数
         */
        protected Object[] args;
        /**
         *  执行结果
         */
        protected Object res;
        /**
         *  执行异常
         */
        protected Throwable e;
        /**
         *  执行耗时
         */
        protected long timeOfExecute;

        public static Result build(ShardInfo shardInfo, boolean success, Object[] args, Object res, Throwable e, long time) {
            Result r = new Result();
            r.shardInfo = shardInfo;
            r.success = success;
            r.args = args;
            r.res = res;
            r.e = e;
            r.timeOfExecute = time;
            return r;
        }
        public static Result success(ShardInfo shardInfo, Object res) {
            return build(shardInfo, true, null, res, null, 0);
        }
        public static Result error(ShardInfo shardInfo, Exception e) {
            return build(shardInfo, false, null, null, e, 0);
        }

        public ShardInfo getShardInfo() {
            return shardInfo;
        }

        public void setShardInfo(ShardInfo shardInfo) {
            this.shardInfo = shardInfo;
        }

        public boolean isSuccess() {
            return success;
        }

        public void setSuccess(boolean success) {
            this.success = success;
        }

        public Result success(boolean success) {
            setSuccess(success);
            return this;
        }

        public Object getRes() {
            return res;
        }

        public void setRes(Object res) {
            this.res = res;
        }

        public Result res(Object res) {
            setRes(res);
            return this;
        }

        public Throwable getE() {
            return e;
        }

        public void setE(Throwable e) {
            this.e = e;
        }

        public Result e(Exception e) {
            setE(e);
            return this;
        }

        public long getTimeOfExecute() {
            return timeOfExecute;
        }

        public void setTimeOfExecute(long timeOfExecute) {
            this.timeOfExecute = timeOfExecute;
        }

        public Result timeOfExecute(long timeOfExecute) {
            setTimeOfExecute(timeOfExecute);
            return this;
        }

        public Object[] getArgs() {
            return args;
        }

        public void setArgs(Object[] args) {
            this.args = args;
        }
    }
}
